#include "comProtocol.h"
#include "intrin.h"
#include <string>
#include "xmlParser/xmlParser.h"
#include <sstream>

using namespace std;

// ----------
// Pomocna funkce - prevadi int na retezec
string ComData::intToString (int i) {
	stringstream ss;
	ss << i;
	return ss.str();
}

// ----------
// Pomocna funkce - prevadi pole na retezec, kde jednotlive polozky jsou oddeleny ';'
string ComData::arrayToString(int* a, int length) {
	string result = "";
	for(int i = 0; i < length; i++) {
		result.append(ComData::intToString(a[i]));
		if(i != length-1) {
			result.append(";");
		}
	}

	return result;
}

// ----------
// Pomocna funkce - prevadi retezec na pole (jednotlive polozky v retezci jsou oddeleny ';')
// vraci pocet prvku v poli
int ComData::stringToArray(std::string str, int* a) {
    string::size_type lastPos = str.find_first_not_of(";", 0);
    string::size_type pos     = str.find_first_of(";", lastPos);
		int index = 0;

    while (string::npos != pos || string::npos != lastPos)
    {
        a[index] = atoi(str.substr(lastPos, pos - lastPos).c_str());
        lastPos = str.find_first_not_of(";", pos);
        pos = str.find_first_of(";", lastPos);
				index++;
    }

		return index;
}

// ----------
// Prevadi strukturu ComData na XML reprezentaci
string ComData::ToXML() {
	// hlavicka
	XMLNode rootNode = XMLNode::createXMLTopNode("xml", TRUE);
	rootNode.addAttribute("version", "1.0");
	
	// korenovy element + namespaces
	XMLNode messageNode = rootNode.addChild("zprava");
	messageNode.addAttribute("xmlns", "http://checkers-machine-learning.googlecode.com/protocol");
	messageNode.addAttribute("xmlns:ml", "http://checkers-machine-learning.googlecode.com/protocol-ml-ext");

	// typ zpravy
	string msgType = this->ComMessageToString();
	messageNode.addAttribute("druh_zpravy", msgType.c_str());
	
	// ----------
	// v zavislosti na typu zpravy pridava dalsi elementy

	// zpravy obsahujici <identifikace>
	if(this->Msg == HELLO) {
		XMLNode identNode = messageNode.addChild("identifikace");
		identNode.addAttribute("jmeno", this->Identification.c_str());
		switch(this->Player) {
			case WHITE: identNode.addAttribute("barva", "1"); break;
			case BLACK: identNode.addAttribute("barva", "2"); break;
		}
	}
	
	// zpravy obsahujici <hraci_plocha>
	if(this->Msg == DO_MOVE || this->Msg == MOVE_OK || this->Msg == MOVE_DID_NOT_CAPTURE ||
		this->Msg == GAME_DRAWN || this->Msg == GAME_WON || this->Msg == GAME_LOST) {
		XMLNode boardNode = messageNode.addChild("hraci_plocha");
		boardNode.addAttribute("kol_bez_vyhozeni", ComData::intToString(this->CurrentBoard.roundsWithoutCapture).c_str());
		boardNode.addAttribute("sachovnice", this->BoardToString().c_str());
	}

	// zpravy obsahujici <tah>
	if(this->Msg == MOVE_SENT || this->Msg == DO_MOVE) {
		// -1 v poli FROM znaci, ze tah je neplatny (pouziva se u DO_MOVE, pokud jeste nebyl odehraz zadny tah)
		if(this->CurrentMove.From != -1) {

			XMLNode moveNode = messageNode.addChild("tah");
			// +1 protoze v komunikacnik protokolu indexujeme od 1, v programu od 0
			moveNode.addAttribute("od", ComData::intToString(this->CurrentMove.From +1).c_str());
			for(int i = 0; i < this->CurrentMove.ToCount; i++) {
				this->CurrentMove.To[i] += 1;
			}
			moveNode.addAttribute("do", ComData::arrayToString((int*)this->CurrentMove.To, this->CurrentMove.ToCount).c_str());
			for(int i = 0; i < this->CurrentMove.CapturedCount; i++) {
				this->CurrentMove.Captured[i] += 1;
			}
			moveNode.addAttribute("vyhozeni", ComData::arrayToString((int*)this->CurrentMove.Captured, this->CurrentMove.CapturedCount).c_str());
	
			// rozsireni protokolu nutne pro strojove uceni
			moveNode.addAttribute("ml:fitnessComponents", ComData::arrayToString(this->CurrentMove.FitnessComponents, 6).c_str());
			moveNode.addAttribute("ml:fitnessTotal", ComData::intToString(this->CurrentMove.FitnessTotal).c_str());
		}
	}

	if(this->Msg == GAME_ERROR) {
		XMLNode  errorNode = messageNode.addChild("error");
		errorNode.addText(this->errorMsg.c_str());
	}

	// prevede na retezec
	string result = rootNode.createXMLString();
	return result;
}

// ----------
// parsuje XML reprezentaci struktury ComData
void ComData::FromXML(std::string s) {
	// typ zpravy
	XMLNode messageNode = XMLNode::parseString(s.c_str(), "zprava");
	string messageType = messageNode.getAttribute("druh_zpravy");
	this->ComMessageFromString(messageType);

	// ----------
	// v zavislosti na typu zpravy cte dalsi elementy

	if(this->Msg == HELLO) {
		XMLNode identNode = messageNode.getChildNode("identifikace");
		this->Identification = identNode.getAttribute("jmeno");
		int player = atoi(identNode.getAttribute("barva"));
		switch(player) {
			case 1: this->Player = WHITE; break;
			case 2: this->Player = BLACK; break;
		}
	}

	// zpravy obsahujici <hraci_plocha>
	if(this->Msg == DO_MOVE || this->Msg == MOVE_OK || this->Msg == MOVE_DID_NOT_CAPTURE ||
		 this->Msg == GAME_DRAWN || this->Msg == GAME_WON || this->Msg == GAME_LOST) {
		XMLNode boardNode = messageNode.getChildNode("hraci_plocha");
		string board = boardNode.getAttribute("sachovnice");
		this->BoardFromString(board);

		this->CurrentBoard.roundsWithoutCapture = atoi(boardNode.getAttribute("kol_bez_vyhozeni"));
	}

	// zpravy obsahujici <tah>
	if(this->Msg == MOVE_SENT) {
		XMLNode moveNode = messageNode.getChildNode("tah");
		// -1 protoze v komunikacni protokolu jsou pole indexovana od 1 v programu od 0
		this->CurrentMove.From = atoi(moveNode.getAttribute("od")) - 1;
		
		string to = moveNode.getAttribute("do");
		this->CurrentMove.ToCount =  ComData::stringToArray(to.c_str(), (int*)this->CurrentMove.To);
		for(int i = 0; i < this->CurrentMove.ToCount; i++) {
				this->CurrentMove.To[i] -= 1;
		}

		string captured = moveNode.getAttribute("vyhozeni");
		this->CurrentMove.CapturedCount = ComData::stringToArray(captured.c_str(), (int*)this->CurrentMove.Captured);
		for(int i = 0; i < this->CurrentMove.CapturedCount; i++) {
			this->CurrentMove.Captured[i] -= 1;
		}

		// zpravy obsahujici polozky rozsireneho protokolu (nutne pro strojove uceni)
		if(moveNode.isAttributeSet("ml:fitnessComponents")) {
			string fitness = moveNode.getAttribute("ml:fitnessComponents");
			ComData::stringToArray(fitness, this->CurrentMove.FitnessComponents);
		}
		if(moveNode.isAttributeSet("ml:fitnessTotal")) {
			this->CurrentMove.FitnessTotal = atoi(moveNode.getAttribute("ml:fitnessTotal"));
		}
	}
}

// ----------
// Prevede hraci pole do textove reprezentace
string ComData::BoardToString() {
	string result = "";
	for(int i = 0; i < 32; i ++) {
		switch(this->CurrentBoard.At(i)) {
			case 2: result.append("1"); break;
			case 3: result.append("2"); break;
			case 4: result.append("3"); break;
			case 5: result.append("4"); break;
			default: result.append("0");
		}
	}

	return result;
}

// -----------
// Prevede hraci pole z textove reprezentace
void ComData::BoardFromString(string s) {
	for(int i = 0; i < 32; i ++) {
		char checker_c = s.at(i);
		int checker = atoi(&checker_c);

		if(checker == 2) {
			this->CurrentBoard.NormalCheckers |= FieldsMasks[i];
			this->CurrentBoard.Color[BLACK] |= FieldsMasks[i];
		}
		if(checker == 4) {
			this->CurrentBoard.Queens |= FieldsMasks[i];
			this->CurrentBoard.Color[BLACK] |= FieldsMasks[i];
		}
		if(checker == 1) {
			this->CurrentBoard.NormalCheckers |= FieldsMasks[i];
			this->CurrentBoard.Color[WHITE] |= FieldsMasks[i];
		}
		if(checker == 3) {
			this->CurrentBoard.Queens |= FieldsMasks[i];
			this->CurrentBoard.Color[WHITE] |= FieldsMasks[i];
		}
	}
}

// -----------
// Prevede typ zpravy do textove reprezentace

string ComData::ComMessageToString() {
	string result;
	switch(this->Msg) {
		case HELLO: return "HELLO";

		case OK: return "OK";
  
		case DO_MOVE: return "DO_MOVE";
		case MOVE_SENT: return "MOVE_SENT";
		case MOVE_OK: return "MOVE_OK";
		case MOVE_DID_NOT_CAPTURE: return "MOVE_DID_NOT_CAPTURE"; 

		case GAME_WON: return "GAME_WON";
		case GAME_LOST: return "GAME_LOST";
		case GAME_DRAWN: return "GAME_DRAWN";
  
		case GAME_ERROR: return "GAME_ERROR";
	}
}

// -----------
// Prevede typ zpravy z textove reprezentace
void ComData::ComMessageFromString(std::string s) {
	this->Msg = UNKNOWN;

	if(s == "HELLO") this->Msg = HELLO;
	
	if(s == "OK") this->Msg = OK;

	if(s == "DO_MOVE") this->Msg = DO_MOVE;
	if(s == "MOVE_SENT") this->Msg = MOVE_SENT;
	if(s == "MOVE_OK") this->Msg = MOVE_OK;
	if(s == "MOVE_DID_NOT_CAPTURE") this->Msg = MOVE_DID_NOT_CAPTURE;

	if(s == "GAME_WON") this->Msg = GAME_WON;
	if(s == "GAME_LOST") this->Msg = GAME_LOST;
	if(s == "GAME_DRAWN") this->Msg = GAME_DRAWN;

	if(s == "GAME_ERROR") this->Msg = GAME_ERROR;
}